package org.scohen.juploadr.uploadapi.flickrrest;

import java.io.IOException;
import java.io.StringReader;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;

import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.scohen.juploadr.Messages;
import org.scohen.juploadr.upload.HttpClientFactory;
import org.scohen.juploadr.uploadapi.Action;
import org.scohen.juploadr.uploadapi.AuthException;
import org.scohen.juploadr.uploadapi.CommunicationException;
import org.scohen.juploadr.uploadapi.ProtocolException;
import org.scohen.juploadr.uploadapi.flickrrest.authentication.FlickrAccount;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

public abstract class FlickrMethod implements Action {
    protected static final String URL = "http://www.flickr.com/services/rest"; //$NON-NLS-1$
    protected HttpClient client = HttpClientFactory.getHttpClient();
    private URLCodec codec = new URLCodec();
    private DefaultFlickrHandler handler;
    private static Log log = LogFactory.getLog(FlickrMethod.class);
    
    public abstract DefaultFlickrHandler getResponseHandler();

    public boolean execute() throws ProtocolException, CommunicationException {

        HttpMethodBase method = getMethod();

        boolean rv = false;
        try {
            int response = client.executeMethod(method);
            if (HttpStatus.SC_OK == response) {
                rv = parseResponse(method.getResponseBodyAsString());
                if (!rv) {
                    throw defaultExceptionFor(handler.getErrorCode());
                }
            } else {
                throw new CommunicationException(Messages.getString(
                        "juploadr.ui.error.bad.http.response", response)); //$NON-NLS-1$
            }
       } catch (InvalidAuthTokenException iat) {
            ((RestFlickrApi)RestFlickrApi.getInstance()).reauthCurrentAccount();
        } catch (HttpException e) {
            throw new CommunicationException(e.getMessage(), e);
        } catch (IOException e) {
            throw new CommunicationException(e.getMessage(), e);
        } finally {
            method.releaseConnection();
        }
        return rv;

    }

    public int getErrorCode() {
        return handler.getErrorCode();
    }

    private ProtocolException defaultExceptionFor(int errorCode) {
        ProtocolException ex = null;
        switch (errorCode) {
        case 96:
            ex = new InvalidSignatureException();
            break;
        case 97:
            ex = new MissingSignatureException();
            break;
        case 98:
            ex = new InvalidAuthTokenException();
            break;
        case 99:
            ex = new UserNotLoggedInException();
        case 100:
            ex = new InvalidApiKeyException();
            break;
        case 105:
            ex = new ServiceUnavailableException();
            break;
        default:
            ex = buildProtocolException(errorCode);

        }
        if (ex == null) {
            // we still don't have an exception, this is very odd.
            ex = new ProtocolException();
        }
        return ex;
    }

    protected abstract ProtocolException buildProtocolException(int errorCode);

    /**
     * @param responseBodyAsString
     * @return
     * @throws AuthException
     */
    public boolean parseResponse(String response) throws ProtocolException {
        try {
            System.setProperty("org.xml.sax.driver", //$NON-NLS-1$
                    "com.bluecast.xml.Piccolo"); //$NON-NLS-1$
            log.info(response);
            XMLReader reader = XMLReaderFactory.createXMLReader();
            handler = getResponseHandler();
            reader.setContentHandler(handler);
            reader.parse(new InputSource(new StringReader(response)));
            if (!handler.isSuccessful()) {
                throw defaultExceptionFor(handler.getErrorCode());
            }
            return handler.isSuccessful();
        } catch (SAXException e) {
            throw new AuthException(Messages.getString("juploadr.ui.error.response.unreadable.noreason"), //$NON-NLS-1$
                    e);
        } catch (IOException e) {
            // this can't happen
        }
        return false;
    }

    public final String getSignature() {
        StringBuffer sb = new StringBuffer(RestFlickrApi.FLICKR_SHARED_SECRET);
        // now iterate through all the params, and append them
        Map<String, String> params = getParams();
        if (isAuthorized()) {
            FlickrAccount currentAccount = (FlickrAccount) RestFlickrApi.getInstance().getCurrentAccount();
            params.put("auth_token", currentAccount.getToken()); //$NON-NLS-1$
        }
        Iterator iter = params.keySet().iterator();
        while (iter.hasNext()) {
            String paramName = (String) iter.next();
            sb.append(paramName);
            sb.append((String) params.get(paramName));
        }
        return StringSigner.md5(sb.toString());
    }

    public String getQueryString(boolean signed) {
        StringBuffer queryString = new StringBuffer();
        Map<String, String> params = getParams();
        if (isAuthorized()) {
            params.put("auth_token", ((FlickrAccount) RestFlickrApi.getInstance().getCurrentAccount()).getToken()); //$NON-NLS-1$
        }
        Iterator iter = params.keySet().iterator();
        while (iter.hasNext()) {
            String paramName = (String) iter.next();
            appendNVP(queryString, paramName, (String) params.get(paramName));
        }

        if (signed) {
            appendNVP(queryString, "api_sig", getSignature()); //$NON-NLS-1$
        }

        return queryString.toString();
    }

    private void appendNVP(StringBuffer url, String name, String value) {
        if (url.length() == 0) {
            url.append("?"); //$NON-NLS-1$
        }
        char lastChar = url.charAt(url.length() - 1);
        if (lastChar != '&' && lastChar != '?') {
            url.append("&"); //$NON-NLS-1$
        }
        url.append(name);
        url.append("="); //$NON-NLS-1$
        url.append(encode(value));
    }

    protected String encode(String toEnc) {
        try {
            return codec.encode(toEnc);
        } catch (EncoderException e) {

        }
        return null;
    }

    public abstract SortedMap<String, String> getParams();

    public abstract boolean isAuthorized();

    public HttpMethodBase getMethod() {
        StringBuffer query = new StringBuffer(URL);
        query.append(getQueryString(true));
        log.info(query.toString());
        GetMethod get = new GetMethod(query.toString());

        return get;
    }
}
